Go 语言 map 是并发安全的吗?

您所在的位置:网站首页 struct数组 更新 加锁 Go 语言 map 是并发安全的吗?

Go 语言 map 是并发安全的吗?

2023-06-07 22:56| 来源: 网络整理| 查看: 265

原文链接: Go 语言 map 是并发安全的吗?

Go 语言中的 map 是一个非常常用的数据结构,它允许我们快速地存储和检索键值对。然而,在并发场景下使用 map 时,还是有一些问题需要注意的。

本文将探讨 Go 语言中的 map 是否是并发安全的,并提供三种方案来解决并发问题。

先来回答一下题目的问题,答案就是并发不安全。

看一段代码示例,当两个 goroutine 同时对同一个 map 进行写操作时,会发生什么?

package main import "sync" func main() { m := make(map[string]int) m["foo"] = 1 var wg sync.WaitGroup wg.Add(2) go func() { for i := 0; i for i := 0; i sync.RWMutex Map map[string]string } func NewSafeMap() *SafeMap { sm := new(SafeMap) sm.Map = make(map[string]string) return sm } func (sm *SafeMap) ReadMap(key string) string { sm.RLock() value := sm.Map[key] sm.RUnlock() return value } func (sm *SafeMap) WriteMap(key string, value string) { sm.Lock() sm.Map[key] = value sm.Unlock() } func main() { safeMap := NewSafeMap() var wg sync.WaitGroup // 启动多个goroutine进行写操作 for i := 0; i defer wg.Done() safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i)) }(i) } wg.Wait() // 启动多个goroutine进行读操作 for i := 0; i defer wg.Done() fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i))) }(i) } wg.Wait() }

在这个示例中,我们定义了一个 SafeMap 结构体,它包含一个 sync.RWMutex 和一个 map[string]string。

定义了两个方法:ReadMap 和 WriteMap。在 ReadMap 方法中,我们使用读锁来保护对 map 的读取操作。在 WriteMap 方法中,我们使用写锁来保护对 map 的写入操作。

在 main 函数中,我们启动了多个 goroutine 来进行读写操作,这些操作都是安全的。

分片加锁

上例中通过对整个 map 加锁来实现需求,但相对来说,锁会大大降低程序的性能,那如何优化呢?其中一个优化思路就是降低锁的粒度,不对整个 map 进行加锁。

这种方法是分片加锁,将这个 map 分成 n 块,每个块之间的读写操作都互不干扰,从而降低冲突的可能性。

package main import ( "fmt" "sync" ) const N = 16 type SafeMap struct { maps [N]map[string]string locks [N]sync.RWMutex } func NewSafeMap() *SafeMap { sm := new(SafeMap) for i := 0; i index := hash(key) % N sm.locks[index].RLock() value := sm.maps[index][key] sm.locks[index].RUnlock() return value } func (sm *SafeMap) WriteMap(key string, value string) { index := hash(key) % N sm.locks[index].Lock() sm.maps[index][key] = value sm.locks[index].Unlock() } func hash(s string) int { h := 0 for i := 0; i safeMap := NewSafeMap() var wg sync.WaitGroup // 启动多个goroutine进行写操作 for i := 0; i defer wg.Done() safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i)) }(i) } wg.Wait() // 启动多个goroutine进行读操作 for i := 0; i defer wg.Done() fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i))) }(i) } wg.Wait() }

在这个示例中,我们定义了一个 SafeMap 结构体,它包含一个长度为 N 的 map 数组和一个长度为 N 的锁数组。

定义了两个方法:ReadMap 和 WriteMap。在这两个方法中,我们都使用了一个 hash 函数来计算 key 应该存储在哪个 map 中。然后再对这个 map 进行读写操作。

在 main 函数中,我们启动了多个 goroutine 来进行读写操作,这些操作都是安全的。

有一个开源项目 orcaman/concurrent-map 就是通过这种思想来做的,感兴趣的同学可以看看。

sync.Map

最后,在内置的 sync 包中(Go 1.9+)也有一个线程安全的 map,通过将读写分离的方式实现了某些特定场景下的性能提升。

package main import ( "fmt" "sync" ) func main() { var m sync.Map var wg sync.WaitGroup // 启动多个goroutine进行写操作 for i := 0; i defer wg.Done() m.Store(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i)) }(i) } wg.Wait() // 启动多个goroutine进行读操作 for i := 0; i defer wg.Done() v, _ := m.Load(fmt.Sprintf("name%d", i)) fmt.Println(v.(string)) }(i) } wg.Wait() }

有了官方的支持,代码瞬间少了很多,使用起来方便多了。

在这个示例中,我们使用了内置的 sync.Map 类型来存储键值对,使用 Store 方法来存储键值对,使用 Load 方法来获取键值对。

在 main 函数中,我们启动了多个 goroutine 来进行读写操作,这些操作都是安全的。

总结

Go 语言中的 map 本身并不是并发安全的。

在多个 goroutine 同时访问同一个 map 时,可能会出现并发不安全的现象。这是因为 Go 语言中的 map 并没有内置锁来保护对map的访问。

尽管如此,我们仍然可以使用一些方法来实现 map 的并发安全。

一种方法是使用读写锁,在读操作时加读锁,在写操作时加写锁。

另一种方法是分片加锁,将这个 map 分成 n 块,每个块之间的读写操作都互不干扰,从而降低冲突的可能性。

此外,在内置的 sync 包中(Go 1.9+)也有一个线程安全的 map,它通过将读写分离的方式实现了某些特定场景下的性能提升。

以上就是本文的全部内容,如果觉得还不错的话欢迎点赞,转发和关注,感谢支持。

参考文章:

https://zhuanlan.zhihu.com/p/356739568

推荐阅读:

Go 语言切片是如何扩容的?Go 语言数组和切片的区别Go 语言 new 和 make 关键字的区别为什么 Go 不支持 []T 转换为 []interface为什么 Go 语言 struct 要使用 tags


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3